/*
    SN76489 emulation
    by Maxim in 2001 and 2002
    converted from my original Delphi implementation

    I'm a C newbie so I'm sure there are loads of stupid things
    in here which I'll come back to some day and redo

    Includes:
    - Super-high quality tone channel "oversampling" by calculating fractional positions on transitions
    - Noise output pattern reverse engineered from actual SMS output
    - Volume levels taken from actual SMS output

    07/08/04  Charles MacDonald
    Modified for use with SMS Plus:
    - Added support for multiple PSG chips.
    - Added reset/config/update routines.
    - Added context management routines.
    - Removed SN76489_GetValues().
    - Removed some unused variables.
*/

#include "shared.h"

#define NoiseInitialState 0x8000 /* Initial state of shift register */
#define PSG_CUTOFF 0x6           /* Value below which PSG does not output */

/* These values are taken from a real SMS2's output */
static const int PSGVolumeValues[2][16] = {
    {892, 892, 892, 760, 623, 497, 404, 323, 257, 198, 159, 123, 96, 75, 60, 0}, /* I can't remember why 892... :P some scaling I did at some point */
    {892, 774, 669, 575, 492, 417, 351, 292, 239, 192, 150, 113, 80, 50, 24, 0}};

static SN76489_Context SN76489[MAX_SN76489];

void SN76489_Init(int which, int PSGClockValue, int SamplingRate)
{
    SN76489_Context *p = &SN76489[which];
    p->dClock = (float)PSGClockValue / 16 / SamplingRate;
    SN76489_Config(which, MUTE_ALLON, BOOST_ON, VOL_FULL, FB_SEGAVDP);
    SN76489_Reset(which);
}

void SN76489_Reset(int which)
{
    SN76489_Context *p = &SN76489[which];
    int i;

    p->PSGStereo = 0xFF;

    for (i = 0; i <= 3; i++)
    {
        /* Initialise PSG state */
        p->Registers[2 * i] = 1;       /* tone freq=1 */
        p->Registers[2 * i + 1] = 0xf; /* vol=off */
        p->NoiseFreq = 0x10;

        /* Set counters to 0 */
        p->ToneFreqVals[i] = 0;

        /* Set flip-flops to 1 */
        p->ToneFreqPos[i] = 1;

        /* Set intermediate positions to do-not-use value */
        p->IntermediatePos[i] = LONG_MIN;
    }

    p->LatchedRegister = 0;

    /* Initialise noise generator */
    p->NoiseShiftRegister = NoiseInitialState;

    /* Zero clock */
    p->Clock = 0;
}

void SN76489_Shutdown(void)
{
}

void SN76489_Config(int which, int mute, int boost, int volume, int feedback)
{
    SN76489_Context *p = &SN76489[which];

    p->Mute = mute;
    p->BoostNoise = boost;
    p->VolumeArray = volume;
    p->WhiteNoiseFeedback = feedback;
}

void SN76489_SetContext(int which, uint8 *data)
{
    memcpy(&SN76489[which], data, sizeof(SN76489_Context));
}

void SN76489_GetContext(int which, uint8 *data)
{
    memcpy(data, &SN76489[which], sizeof(SN76489_Context));
}

uint8 *SN76489_GetContextPtr(int which)
{
    return (uint8 *)&SN76489[which];
}

int SN76489_GetContextSize(void)
{
    return sizeof(SN76489_Context);
}

void SN76489_Write(int which, int data)
{
    SN76489_Context *p = &SN76489[which];

    if (data & 0x80)
    {
        /* Latch/data byte  %1 cc t dddd */
        p->LatchedRegister = ((data >> 4) & 0x07);
        p->Registers[p->LatchedRegister] =
            (p->Registers[p->LatchedRegister] & 0x3f0) /* zero low 4 bits */
            | (data & 0xf);                            /* and replace with data */
    }
    else
    {
        /* Data byte        %0 - dddddd */
        if (!(p->LatchedRegister % 2) && (p->LatchedRegister < 5))
            /* Tone register */
            p->Registers[p->LatchedRegister] =
                (p->Registers[p->LatchedRegister] & 0x00f) /* zero high 6 bits */
                | ((data & 0x3f) << 4);                    /* and replace with data */
        else
            /* Other register */
            p->Registers[p->LatchedRegister] = data & 0x0f; /* Replace with data */
    }
    switch (p->LatchedRegister)
    {
    case 0:
    case 2:
    case 4: /* Tone channels */
        if (p->Registers[p->LatchedRegister] == 0)
            p->Registers[p->LatchedRegister] = 1; /* Zero frequency changed to 1 to avoid div/0 */
        break;
    case 6:                                             /* Noise */
        p->NoiseShiftRegister = NoiseInitialState;      /* reset shift register */
        p->NoiseFreq = 0x10 << (p->Registers[6] & 0x3); /* set noise signal generator frequency */
        break;
    }
}

void SN76489_GGStereoWrite(int which, int data)
{
    SN76489_Context *p = &SN76489[which];
    p->PSGStereo = data;
}

void SN76489_Update(int which, INT16 **buffer, int length)
{
    SN76489_Context *p = &SN76489[which];
    int i, j;

    for (j = 0; j < length; j++)
    {
        for (i = 0; i <= 2; ++i)
            if (p->IntermediatePos[i] != LONG_MIN)
                p->Channels[i] = (p->Mute >> i & 0x1) * PSGVolumeValues[p->VolumeArray][p->Registers[2 * i + 1]] * p->IntermediatePos[i] / 65536;
            else
                p->Channels[i] = (p->Mute >> i & 0x1) * PSGVolumeValues[p->VolumeArray][p->Registers[2 * i + 1]] * p->ToneFreqPos[i];

        p->Channels[3] = (short)((p->Mute >> 3 & 0x1) * PSGVolumeValues[p->VolumeArray][p->Registers[7]] * (p->NoiseShiftRegister & 0x1));

        if (p->BoostNoise)
            p->Channels[3] <<= 1; /* Double noise volume to make some people happy */

        buffer[0][j] = 0;
        buffer[1][j] = 0;
        for (i = 0; i <= 3; ++i)
        {
            buffer[0][j] += (p->PSGStereo >> (i + 4) & 0x1) * p->Channels[i]; /* left */
            buffer[1][j] += (p->PSGStereo >> i & 0x1) * p->Channels[i];       /* right */
        }

        p->Clock += p->dClock;
        p->NumClocksForSample = (int)p->Clock; /* truncates */
        p->Clock -= p->NumClocksForSample;     /* remove integer part */
        /* Looks nicer in Delphi... */
        /*  Clock:=Clock+p->dClock; */
        /*  NumClocksForSample:=Trunc(Clock); */
        /*  Clock:=Frac(Clock); */

        /* Decrement tone channel counters */
        for (i = 0; i <= 2; ++i)
            p->ToneFreqVals[i] -= p->NumClocksForSample;

        /* Noise channel: match to tone2 or decrement its counter */
        if (p->NoiseFreq == 0x80)
            p->ToneFreqVals[3] = p->ToneFreqVals[2];
        else
            p->ToneFreqVals[3] -= p->NumClocksForSample;

        /* Tone channels: */
        for (i = 0; i <= 2; ++i)
        {
            if (p->ToneFreqVals[i] <= 0)
            { /* If it gets below 0... */
                if (p->Registers[i * 2] > PSG_CUTOFF)
                {
                    /* Calculate how much of the sample is + and how much is - */
                    /* Go to floating point and include the clock fraction for extreme accuracy :D */
                    /* Store as long int, maybe it's faster? I'm not very good at this */
                    p->IntermediatePos[i] = (long)((p->NumClocksForSample - p->Clock + 2 * p->ToneFreqVals[i]) * p->ToneFreqPos[i] / (p->NumClocksForSample + p->Clock) * 65536);
                    p->ToneFreqPos[i] = -p->ToneFreqPos[i]; /* Flip the flip-flop */
                }
                else
                {
                    p->ToneFreqPos[i] = 1; /* stuck value */
                    p->IntermediatePos[i] = LONG_MIN;
                }
                p->ToneFreqVals[i] += p->Registers[i * 2] * (p->NumClocksForSample / p->Registers[i * 2] + 1);
            }
            else
                p->IntermediatePos[i] = LONG_MIN;
        }

        /* Noise channel */
        if (p->ToneFreqVals[3] <= 0)
        {                                           /* If it gets below 0... */
            p->ToneFreqPos[3] = -p->ToneFreqPos[3]; /* Flip the flip-flop */
            if (p->NoiseFreq != 0x80)               /* If not matching tone2, decrement counter */
                p->ToneFreqVals[3] += p->NoiseFreq * (p->NumClocksForSample / p->NoiseFreq + 1);
            if (p->ToneFreqPos[3] == 1)
            { /* Only once per cycle... */
                int Feedback;
                if (p->Registers[6] & 0x4)
                { /* White noise */
                    /* Calculate parity of fed-back bits for feedback */
                    switch (p->WhiteNoiseFeedback)
                    {
                        /* Do some optimised calculations for common (known) feedback values */
                    case 0x0006: /* SC-3000      %00000110 */
                    case 0x0009: /* SMS, GG, MD  %00001001 */
                        /* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */
                        /* since that's (one or more bits set) && (not all bits set) */
                        /* which one?         Feedback=((p->NoiseShiftRegister&p->WhiteNoiseFeedback) && (p->NoiseShiftRegister&p->WhiteNoiseFeedback^p->WhiteNoiseFeedback)); */
                        Feedback = ((p->NoiseShiftRegister & p->WhiteNoiseFeedback) && ((p->NoiseShiftRegister & p->WhiteNoiseFeedback) ^ p->WhiteNoiseFeedback));
                        break;
                    case 0x8005: /* BBC Micro */
                        /* fall through :P can't be bothered to think too much */
                    default: /* Default handler for all other feedback values */
                        Feedback = p->NoiseShiftRegister & p->WhiteNoiseFeedback;
                        Feedback ^= Feedback >> 8;
                        Feedback ^= Feedback >> 4;
                        Feedback ^= Feedback >> 2;
                        Feedback ^= Feedback >> 1;
                        Feedback &= 1;
                        break;
                    }
                }
                else /* Periodic noise */
                    Feedback = p->NoiseShiftRegister & 1;

                p->NoiseShiftRegister = (p->NoiseShiftRegister >> 1) | (Feedback << 15);

                /* Original code: */
                /*          p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | ((p->Registers[6]&0x4?((p->NoiseShiftRegister&0x9) && (p->NoiseShiftRegister&0x9^0x9)):p->NoiseShiftRegister&1)<<15); */
            }
        }
    }
}
